import { zalert, toast } from '@/utils/utils'
import { API_BASE_URL } from '@/utils/apiUrl'

type RequestOptionsMethod = 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'

interface BaseConfig {
  url: string
  method: RequestOptionsMethod
  header?: Record<string, any>
  data?: Record<string, any>
  params?: Record<string, any>
  showLoading?: boolean
}

export interface CustomSuccessData<T = any> {
  code: number
  msg?: string
  message?: string
  data?: T
  [keys: string]: any
}


function ajaxFilter(data: any) {
  let status = true
  if (data?.code == 401) {
    status = false
    zalert('Login status invalid', () => {
      uni.removeStorageSync('token')
      uni.reLaunch({ url: '/pages/login/index' })
    })
  }
  return status
}

function getCurrentDomain() {
  const host = (typeof window !== 'undefined' && window.location?.host) || ''
  if (host.includes('localhost') || host.includes('127.0.0.1') || host.includes('192.168.0.71')) {
    
    return 'xhero.vip'
  }
  return host 
}

function buildUrl(url: string, params?: Record<string, any>) {
  if (!params || !Object.keys(params).length) return url
  const usp = new URLSearchParams()
  const append = (key: string, value: any) => {
    if (value === null || typeof value === 'undefined') return
    if (typeof value === 'object') {
      Object.keys(value).forEach(k => usp.append(`${key}[${k}]`, String(value[k])))
    } else {
      usp.append(key, String(value))
    }
  }
  Object.keys(params).forEach(k => append(k, params[k]))
  const qs = usp.toString()
  return qs ? (url.includes('?') ? `${url}&${qs}` : `${url}?${qs}`) : url
}

function sleep(ms: number) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

function shouldRetry(statusCode?: number, errMsg?: string) {
  if (statusCode && [502, 503, 504].includes(statusCode)) return true
  const msg = (errMsg || '').toLowerCase()
  
  if (msg.includes('timeout') || msg.includes('request:fail') || msg.includes('network')) return true
  return false
}


interface RetryOptions {
  maxRetries?: number        
  baseDelayMs?: number       
  jitterMs?: number          
}

const defaultRetry: Required<RetryOptions> = {
  maxRetries:10,
  baseDelayMs: 500,
  jitterMs: 200,
}

/**
 * （ token、platform， 502/）
 */
const baseRequest = async <T>(config: BaseConfig, retryOptions: RetryOptions = {}): Promise<CustomSuccessData<T>> => {
  const opts = { ...defaultRetry, ...retryOptions }

  
  let finalUrl = config.url
  if (config.method === 'GET' && config.params) {
    finalUrl = buildUrl(finalUrl, config.params)
  }

  const Url = API_BASE_URL()

  
  const header = {
    ...(config.header || {}),
    'x-token': uni.getStorageSync('token') || '',
    'h5-platform': getCurrentDomain(),
  }

  
  let attempt = 0
  let lastErr: any = null

  while (attempt <= opts.maxRetries) {
    try {
      
      if (!config.showLoading) {
        uni.hideLoading()
      }

      const res: UniApp.RequestSuccessCallbackResult = await new Promise((resolve, reject) => {
        uni.request({
          url: Url + finalUrl,
          method: config.method || 'GET',
          header,
          data: config.data || {},
          success: resolve,
          fail: reject,
        })
      })

      
      if ((res as any).data?.code == 77) {
        uni.hideLoading()
        const hasVisited = uni.getStorageSync('hasVisited')
        if (hasVisited) {
          uni.navigateTo({ url: '/pages/login/index' })
        }
      }

      if (!config.showLoading) {
        uni.hideLoading()
      }

      
      if (res.statusCode === 200) {
        const data = (res as any).data
        if (ajaxFilter(data)) {
          return data as CustomSuccessData<T>
        } else {
          
          throw { statusCode: 401, data }
        }
      }

      
      if (shouldRetry(res.statusCode)) {
        if (attempt < opts.maxRetries) {
          attempt++
          const delay = opts.baseDelayMs * Math.pow(2, attempt - 1) + Math.floor(Math.random() * opts.jitterMs)
          await sleep(delay)
          continue
        } else {
          lastErr = res
          break
        }
      }

      
      const msg = (res as any).data?.msg || 'Request failed'
      if ((res as any).data?.code != 401) {
        toast(msg)
      }
      throw res
    } catch (err: any) {
      
      lastErr = err
      const errMsg: string = err?.errMsg || err?.message || ''

      if (shouldRetry(undefined, errMsg) && attempt < opts.maxRetries) {
        attempt++
        const delay = opts.baseDelayMs * Math.pow(2, attempt - 1) + Math.floor(Math.random() * opts.jitterMs)
        await sleep(delay)
        continue
      } else {
        
        if (!config.showLoading) {
          uni.hideLoading()
        }
        if (errMsg.includes('timeout')) {
          zalert('Request timed out, please try again later')
        } else if (errMsg.includes('request')) {
          zalert('The server has been shut down')
        } else if (err?.statusCode && [502, 503, 504].includes(err.statusCode)) {
          zalert(`Request failed (${err.statusCode}), please try again later`)
        } else if (err?.statusCode && err?.data?.msg) {
          zalert(err.data.msg)
        } else {
          zalert('Request failed, please check the network')
        }
        throw err
      }
    }
  }

  
  throw lastErr || new Error('Unknown request error')
}

export default baseRequest
